import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.swt.SWT;
import org.eclipse.zest.core.widgets.Graph;
import org.eclipse.zest.core.widgets.GraphConnection;
import org.eclipse.zest.core.widgets.GraphNode;

/**
 * 
 * This class reads/writes Graph nodes and connections to/from data files.
 *
 */
public class BaphiFileHandler {
	private Graph g;
	private NodeUtils nu;
	
	/**
	 * This class reads/writes Graph nodes and connections to/from data files.
	 * @param g
	 * @param nu
	 */
	public BaphiFileHandler(Graph g, NodeUtils nu)
	{
		this.g = g;
		this.nu = nu;
	}
	
	/**
	 * Takes the provided file name and path, opens the file, parses it for nodes and connections, and adds them to the Graph.
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	public void readBaphiGraph(String fileName)
	{
		File file = new File(fileName);
		
		try
		{
			GraphNode node;
			Scanner scanner = new Scanner(file);
			scanner.useDelimiter(" |,|\n");

			// First pass: create nodes and ignore connections
			while (scanner.hasNext())
			{
				String nodeName, colorStr;
				int x,y;
				NodeColorUtils.NodeColor nodeColor;

				String str = scanner.next();
				
				// Read line in the form of "Node <name> <color> <x>,<y>"
				if (!str.equalsIgnoreCase("Node"))
					throw new java.util.NoSuchElementException("Expected keyword 'Node' or 'ConnectsTo' but found " + str);
				nodeName = scanner.next();
				colorStr = scanner.next();
				x = scanner.nextInt();
				y = scanner.nextInt();
				nodeColor = nu.colorLookup(colorStr);
				if (nodeColor == NodeColorUtils.NodeColor.Error)
					throw new java.util.NoSuchElementException("Expected a node color but found " + colorStr);

				node = new GraphNode(g, SWT.NONE, nodeName, nu.getImage(nodeColor.getIdx()));
				node.setLocation(x, y);

				str = scanner.next();
				 
				if (!str.equalsIgnoreCase("ConnectsTo"))
					continue;

				// Throw away "ConnectsTo" lines for now
				scanner.nextLine();
			}
			
			scanner.close();
			
			scanner = new Scanner(file);
			scanner.useDelimiter(" |,|\n");
			
			// Second pass: create connections now that we have all the nodes created
			while (scanner.hasNext())
			{
				String str = scanner.next();
				String nodeName;
				
				if (!str.equalsIgnoreCase("Node"))
					throw new java.util.NoSuchElementException("Expected keyword 'Node' or 'ConnectsTo' but found " + str);
				nodeName = scanner.next();
				
				List<GraphNode> nodeList = g.getNodes();
				
				node = null;
				
				// Look up this node in the Graph
				for (GraphNode gn : nodeList)
				{
					if (gn.getText().equalsIgnoreCase(nodeName))
					{
						node = gn;
						break;
					}
				}
				
				if (node == null)
					throw new java.util.NoSuchElementException("Lookup failed for node " + nodeName);
				
				scanner.nextLine(); // throw away the rest of the Node line; we already have this stuff loaded.
				str = scanner.next();
				 
				if (!str.equalsIgnoreCase("ConnectsTo"))
					continue;

				// Read line in the form of "ConnectsTo <nodeName>[,<nodeName>,...]"
				
				String line = scanner.nextLine();
				Scanner connectionScanner = new Scanner(line);
				connectionScanner.useDelimiter(" |,|\n");
				
				while (connectionScanner.hasNext())
				{
					String connectNode = connectionScanner.next();
					
					for (GraphNode gn : nodeList)
					{
						if (gn.getText().equalsIgnoreCase(connectNode))
						{
							// use the newConnection method in NodeUtils, which makes sure we don't have duplicates.
							nu.newConnection(node, gn);
							break;
						}
					}
				}
			}
		}
		catch(FileNotFoundException e)
		{
			e.printStackTrace();
			return;
		}
		catch(NoSuchElementException e)
		{
			e.printStackTrace();
			return;
		}

	}
	
	/**
	 * Takes the provided file name and path, creates the file, and writes the contents of the Graph out to it.
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	public void writeBaphiGraph(String fileName)
	{	
		boolean connectionFound, needComma;
		
		try
		{
			FileWriter fw = new FileWriter(fileName);

			for (GraphNode gn : (List<GraphNode>) g.getNodes())
			{
				Point point = gn.getLocation();
				NodeColorUtils.NodeColor nc = (NodeColorUtils.NodeColor) gn.getData();
				int nodeColorIdx;
				if (nc == null)
					nodeColorIdx = 0;
				else
					nodeColorIdx = nc.getIdx();
				
				// Write Node line
				fw.write("Node " + gn.getText() + " " + nu.getColorString(nodeColorIdx) + "," + point.x + "," + point.y + "\n");

				connectionFound = false;
				needComma = false;
				
				// Write ConnectsTo line, if any connections exist for this Node
				for (GraphConnection cn : (List<GraphConnection>) gn.getSourceConnections())
				{			
					GraphNode gn2 = cn.getDestination();
					
					if (gn == gn2)
						continue;
					
					if (!connectionFound)
					{
						connectionFound = true;
						fw.write("ConnectsTo ");
					}
					
					if (needComma)
						fw.write(",");
					else
						needComma = true;
					
					fw.write(gn2.getText());
				}
				
				//TODO Do we really need to check both Source and Target connections?
				for (GraphConnection cn : (List<GraphConnection>) gn.getTargetConnections())
				{			
					GraphNode gn2 = cn.getSource();
					
					if (gn == gn2)
						continue;
					
					if (!connectionFound)
					{
						connectionFound = true;
						fw.write("ConnectsTo ");
					}
					
					if (needComma)
						fw.write(",");
					else
						needComma = true;
					
					fw.write(gn2.getText());
				}
				
				if (connectionFound)
					fw.write("\n");
				
				fw.write("\n");
			}
			
			fw.close();
		}
		catch(IOException e)
		{
			e.printStackTrace();
			return;
		}
	}
}
